import json
import logging
import os.path
import time
import traceback
from concurrent.futures import ThreadPoolExecutor
from queue import Queue
from threading import Thread

from protocol_payload import ProtocolPayload
from device import Device, CtlAction
from socket_client import SocketClient
from config import *

_device = Device(SERIAL_PORT, device_id=DEVICE_ID)
_client = SocketClient(HOST, SOCKET_PORT)

cmd_stack = Queue()

msg_stack = Queue()

run_device_thread_pool = ThreadPoolExecutor(max_workers=1)


def on_error(e: Exception):
    if isinstance(e, ConnectionResetError) and e.errno in (10054, 10061):
        socket_init()
    traceback.print_exc()


def logging_init():
    fmt = '%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s: %(message)s'
    if not os.path.exists(LOG_PATH):
        os.mkdir(LOG_PATH)
    logging.basicConfig(level=logging.DEBUG,
                        format=fmt,
                        filename='%s/%s-log.txt' % (LOG_PATH, time.strftime("%Y-%m-%d")),
                        filemode='a',
                        datefmt='%Y-%m-%d %H:%M:%S'
                        )


def ctl_device(_device: Device):
    def invoke(callback, *args):
        callback(*args)

    while True:
        try:
            payload: ProtocolPayload = cmd_stack.get()
            action = payload.action

            if action == CtlAction.MOVE_TO.value:
                run_device_thread_pool.submit(fn=_device.move, start=payload.payload['start'],
                                              end=payload.payload['end'])

            elif action == CtlAction.REGISTER.value or action == CtlAction.READ_POSITION.value:
                print('接收到读取位置的命令')
                pass
            elif action == CtlAction.MOVE.value:
                run_device_thread_pool.submit(fn=_device.move_to_position, position=payload.payload['position'])

            else:
                logging.error('没有找到对应的命令 CMD=%s' % (payload.to_json()))
                continue
            position = _device.read_position()
            response = ProtocolPayload(_device.get_device_id(), {
                'position': position,
            })
            response.cmd_id = payload.cmd_id
            response.action = CtlAction.READ_POSITION.value
            msg_stack.put(response)
        except BaseException as e:
            on_error(e)


def listener_remote_ctl(client: SocketClient):
    while True:
        resp: ProtocolPayload = client.message(on_error=on_error)
        # sleep 让线程释放锁，保证 心跳消息的发送
        if resp is not None:
            cmd_stack.put(resp)
        time.sleep(0.001)


def heartbeat():
    def on_success(msg):
        print('心跳消息响应=%s' % (msg.to_json()))
        pass

    while True:
        heart_beat = ProtocolPayload(device_id=DEVICE_ID, status=_device.status())
        heart_beat.action = CtlAction.HEARTBEAT.value
        print('心跳消息请求=%s' % (heart_beat.to_json()))
        _client.send(heart_beat, on_success=on_success, on_error=on_error)
        time.sleep(30)


def register_device(_device: Device):
    def on_success(msg):
        print('注册设备响应=%s' % msg)

    register_msg = ProtocolPayload(_device.get_device_id(), _device.status(), action=CtlAction.REGISTER)
    print('注册设备请求=%s' % (register_msg.to_json()))
    return _client.send(register_msg, on_success=on_success, on_error=on_error);


# socket 重新连接次数
SOCKET_CONNECT_RETRY_COUNT = 1


def socket_init():
    global SOCKET_CONNECT_RETRY_COUNT
    try:
        _client.connect()
        logging.info('服务端连接成功 host = %s:%d' % (HOST, SOCKET_PORT))
    except Exception as e:
        print(repr(e))
        logging.error('服务端连接异常 host = %s:%d ,Exception = [%s]   尝试第 [%d]次重新连接' % (
            HOST, SOCKET_PORT, repr(e), SOCKET_CONNECT_RETRY_COUNT))
        SOCKET_CONNECT_RETRY_COUNT = SOCKET_CONNECT_RETRY_COUNT + 1
        time.sleep(3)
        socket_init()


# 设备重新连接次数
DEVICE_CONNECT_RETRY_COUNT = 1

device_connect_retry_thread: Thread = None


def device_init():
    global DEVICE_CONNECT_RETRY_COUNT
    global device_connect_retry_thread
    try:
        _device.connect()
    except Exception as e:
        logging.error(
            '设备连接异常 deviceId = %d ,Exception = [%s]   尝试第 [%d]次重新连接' % (DEVICE_ID, repr(e), DEVICE_CONNECT_RETRY_COUNT))
        DEVICE_CONNECT_RETRY_COUNT = DEVICE_CONNECT_RETRY_COUNT + 1
        time.sleep(3)

        if DEVICE_CONNECT_RETRY_COUNT > 5:
            Thread(target=device_init).start()
        else:
            device_init()


def send_msg(socket: SocketClient):
    while True:
        msg = msg_stack.get()
        socket.send(msg)


if __name__ == '__main__':
    logging_init()

    socket_init()
    response = register_device(_device)

    # 用来定期发送心跳消息，确保服务端关闭seesion
    heartbeat_thread = Thread(target=heartbeat, daemon=True, name="心跳消息")
    heartbeat_thread.start()

    # 监听服务端推送过来的控制命令
    listener_cmd_thread = Thread(target=listener_remote_ctl, args=(_client,), daemon=True, name="监听远程命令")
    listener_cmd_thread.start()

    # 回复服务端发送过来的控制命令
    send_cmd_ctl_thread = Thread(target=send_msg, args=(_client,), daemon=True, name="发送消息线程")
    send_cmd_ctl_thread.start()

    device_init()

    # 控制设备的运行
    ctl_device_thread = Thread(target=ctl_device, args=(_device,), daemon=True, name="控制设备运行线程")
    ctl_device_thread.start()

    ctl_device_thread.join()
    listener_cmd_thread.join()
    heartbeat_thread.join()
    send_cmd_ctl_thread.join()

    run_device_thread_pool.shutdown()
    logging.info('shutdown')
